﻿using System;
using System.Reflection;
using System.Text;
using OsamesFrameworkCore.Logging;
using OsamesFrameworkCore.Tools;
using OsamesFrameworkCore.Xml.Types;

namespace OsamesFrameworkCore.Plugins.Tools
{
    /// <summary>
    /// Utility tools for plugins.
    /// </summary>
    public static class PluginsTools
    {
        /// <summary>
        /// Returns a plugin unique ID (plugin name in xml config, or if not defined, type fullname).
        /// It's used as a key for dictionary of available plugins in plugins manager.
        /// </summary>
        /// <param name="iPlugin_">Plugin</param>
        /// <returns>Name from xml configuration or type fullname</returns>
        public static string GetPluginUniqueId(IPlugin iPlugin_)
        {
            bool bOk = true;
            string strId = null;
            string strExceptionMessage = null;
            try
            {
                strId = ConfigurationTools.FindValue(iPlugin_.XmlDictPluginConfiguration, "sectionBase", "pluginName");
                bOk = !string.IsNullOrEmpty(strId);
            }
            catch (XmlDictionaryException ex)
            {
                bOk = false;
                strExceptionMessage = ex.Message;
            }
            finally
            {
                if (!bOk)
                {
                    string strFullName = iPlugin_.GetType().FullName;
                    strId = strFullName;
                    string strMessage =
                        string.Format(
                            "No xml definition for plugin name, unique ID defaulting to type fullname for type {0} (Exception : {1})",
                            strFullName, strExceptionMessage ?? "NONE");
                    LoggerService.Log(typeof(PluginsTools), LogLevel.Warn, strMessage);
                    LoggerService.LogToOutput(strMessage);
                }
            } return strId;
        }
        
        /// <summary>
        /// Checks that a given function exists in a plugin and is public and not static. Also check that arguments length and types are compatible.
        /// </summary>
        /// <param name="strFunctionName_">Function to call name</param>
        /// <param name="oParameters_">Parameters for function call</param>
        /// <param name="iPlugin_">Plugin</param>
        /// <returns></returns>
        public static object Execute(string strFunctionName_, object[] oParameters_,  IPlugin iPlugin_)
        {
            // TODO corresponding unit tests
            // Does the function exist in this plugin ? (public and not static)
            MethodInfo miMethod = iPlugin_.GetType().GetMethod(strFunctionName_, BindingFlags.Public | BindingFlags.Instance);
            if(miMethod == null)
            {
                throw new PluginOperationException(string.Format(PluginOperationException.CST_STR_ERROR_METHOD_0_NOT_FOUND_PLUGIN_1, strFunctionName_, GetPluginUniqueId(iPlugin_)));
            }

            // Do parameters of function, and oParameters_ match ?
            ParameterInfo[] piInfo = miMethod.GetParameters();

            int iCount = piInfo.Length;

            if (iCount != oParameters_.Length)
            {
                throw new PluginOperationException(string.Format(PluginOperationException.CST_STR_ERROR_METHOD_0_PARAMS_MISMATCH_1_PLUGIN_2, strFunctionName_, "Wrong count for call", GetPluginUniqueId(iPlugin_)));
            }
            StringBuilder sbIncorrectParams = new StringBuilder();
            for(int i = 0; i < iCount; i++)
            {
                // Check parameters types
                if (oParameters_[i] == null)
                    continue; // may generate error later in plugin
               Type tExpectedParamType = piInfo[i].GetType();
                Type tActualType = oParameters_[i].GetType();
               if(tActualType == tExpectedParamType)
                   continue;
                sbIncorrectParams.Append(string.Format("Param at index {0} is of type {1} instead of {2} - ", i,
                                                       tActualType, tExpectedParamType));
            }
            string strResult = sbIncorrectParams.ToString();
            if(!string.IsNullOrEmpty(strResult))
                throw new PluginOperationException(string.Format(PluginOperationException.CST_STR_ERROR_METHOD_0_PARAMS_MISMATCH_1_PLUGIN_2, strFunctionName_, strResult, GetPluginUniqueId(iPlugin_)));

            // call function now
            return miMethod.Invoke(iPlugin_, oParameters_);
        }
    }
}
